#ifndef __baker_Array_h__
#define __baker_Array_h__

#include "Prerequire.h"
#include "Callback.h"

namespace baker
{
    enum { ARRAY_NOT_FOUND = 0xffffffff };

    /** Template of a dynamic growing Array
    */
    template < typename T >
    class Array
    {
    public:
        /** Construct an empty Array
        */
        Array()
            : mArray( NULL )
        {
            mArray = g_array_new( FALSE, FALSE, sizeof( T ) );
        }

        /** Destructor
        */
        ~Array()
        {
            g_assert( mArray );

            g_array_free( mArray, TRUE );
        }

        /** Get the number of elements stored in this Array
        */
        inline guint length() const
        {
            return mArray->len;
        }

        /** Append an element to this Array (i.e. add to back).
        */
        inline void append( const T & elem )
        {
            g_assert( mArray );

            g_array_append_val( mArray, elem );
        }

        /** Prepend an element to this Array (i.e. add to front).
        */
        inline void prepend( const T & elem )
        {
            g_assert( mArray );

            g_array_prepend_val( mArray, elem );
        }

        /** Find the index of an element
        */
        inline guint indexOf( const T & elem
                            , guint offset = 0
                            ) const
        {
            for ( guint i = offset; i < mArray->len; ++i )
            {
                if ( at( i ) == elem )
                    return i;
            }

            return ARRAY_NOT_FOUND;
        }

        /** Remove an element using the operator ==
        */
        inline void remove( const T & elem )
        {
            guint idx = indexOf( elem );

            if ( idx != ARRAY_NOT_FOUND )
                removeAt( idx );
        }

        /** Remove an element by index
        */
        inline void removeAt( guint index )
        {
            g_assert( mArray );
            g_assert( index < mArray->len );

            g_array_remove_index( mArray, index );
        }

        /** RemoveRemove an element using the operator ==.
            The last element in the array is used to fill in the space,
            so this function does not preserve the order of the Array.
        */
        inline void removeFast( const T & elem )
        {
            guint idx = indexOf( elem );

            if ( idx != ARRAY_NOT_FOUND )
                removeAtFast( idx );
        }

        /** RemoveRemove an element by index.
            The last element in the array is used to fill in the space,
            so this function does not preserve the order of the Array.
        */
        inline void removeAtFast( guint index )
        {
            g_assert( mArray );
            g_assert( index < mArray->len );

            g_array_remove_index_fast( mArray, index );
        }

        /** get an element by index
        */
        inline T & at( guint index )
        {
            g_assert( mArray );
            g_assert( index < mArray->len );

            return g_array_index( mArray, T, index );
        }

        inline const T & at( guint index ) const
        {
            g_assert( mArray );
            g_assert( index < mArray->len );

            return g_array_index( mArray, T, index );
        }

        inline T & operator [] ( guint index )
        {
            return at( index );
        }

        inline const T & operator [] ( guint index ) const
        {
            return at( index );
        }

    private:
        GArray * mArray;
    };


    /** apply a callback function for each of the element inside the input Array

        @param array The target array
        @param callback The callback to be apply to each element in the array
    */
    template< typename T
            , typename F
            >
    static inline void foreach  ( Array<T> & array
                                , const F & callback
                                )
    {
        const guint len = array.length();

        cb::signal< void, T & > s;
        s.connect( callback );

        for ( guint i = 0; i < len; ++i )
        {
            s( array.at( i ) );
        }
    }

    template< typename T
            , typename F
            >
    static inline void foreach  ( const Array<T> & array
                                , const F & callback
                                )
    {
        const guint len = array.length();

        cb::signal< void, const T & > s;
        s.connect( callback );

        for ( guint i = 0; i < len; ++i )
        {
            s( array.at( i ) );
        }
    }

    /** Return the index of the minimun item in this Array.
        The smallest index is returned if two or more items are equal in this Array.

        @param array The target array
        @param compareLess A callback to compare lhs < rhs
    */
    template< typename T
            , typename F
            >
    static inline guint indexOfMin  ( const Array<T> & array
                                    , const F & compareLess
                                    )
    {
        const guint len = array.length();

        if ( 0 == len )
        {
            return ARRAY_NOT_FOUND;
        }

        cb::signal< bool, const T &, const T & > s;
        s.connect( compareLess );

        guint minIdx = 0;
        const T * minItem = &array.at( 0 );

        for ( guint i = 1; i < len; ++i )
        {
            const T * currItem = &array.at( i );

            // check if *currItem < * minItem
            if ( s( *currItem, *minItem ) )
            {
                minItem = currItem;
                minIdx = i;
            }
        }

        return minIdx;
    }
}

#endif  // __baker_Array_h__
